package com.github.fork.spring;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.YamlProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.resolver.Resolver;

import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;

/**
 * @author Jerry Liu
 * @since 2017/3/20
 */
public class SpringYamlPropertiesFactoryBean implements
        FactoryBean<Properties>, InitializingBean, EnvironmentAware {


    private boolean singleton = true;
    private Properties properties;
    private Resource resource;
    private Environment environment;

    @Override
    public Properties getObject() throws Exception {
        return this.properties;
    }

    @Override
    public Class<?> getObjectType() {
        return Properties.class;
    }

    @Override
    public boolean isSingleton() {
        return this.singleton;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (isSingleton()) {
            if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
                this.properties = new Properties();
                loadDefault();
                for (int i = 0; i < environment.getActiveProfiles().length; i++) {
                    String profile = environment.getActiveProfiles()[i];
                    Processor processor = new Processor(resource, profile);
                    this.properties.putAll(processor.process());
                }
                //让VM参数可以覆盖YAML
                this.properties.putAll(System.getProperties());
                System.getProperties().putAll(this.properties);
            }
        }
    }

    protected void loadDefault(){
        Processor processor = new Processor(resource, null);
        this.properties.putAll(processor.process());
    }

    public void setResource(Resource resource){
        this.resource = resource;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    /**
     * {@link YamlProcessor} to create a {@link Map} containing the property values.
     * Similar to {@link org.springframework.beans.factory.config.YamlPropertiesFactoryBean}
     *  but retains the order of entries.
     */
    private static class Processor extends YamlProcessor {

        Processor(Resource resource, String profile) {
            if (profile == null) {
                setMatchDefault(true);
                setDocumentMatchers(new SpringProfileDocumentMatcher());
            } else {
                setMatchDefault(false);
                setDocumentMatchers(new SpringProfileDocumentMatcher(profile));
            }
            setResources(new Resource[]{resource});
        }

        @Override
        protected Yaml createYaml() {
            return new Yaml(new StrictMapAppenderConstructor(), new Representer(),
                    new DumperOptions(), new Resolver() {

                @Override
                public void addImplicitResolver(Tag tag, Pattern regexp,
                                                String first) {
                    if (tag == Tag.TIMESTAMP) {
                        return;
                    }
                    super.addImplicitResolver(tag, regexp, first);
                }
            });
        }

        public Properties process() {
            final Properties result = new Properties();
            process(new MatchCallback() {
                @Override
                public void process(Properties properties, Map<String, Object> map) {
                    result.putAll(properties);
                }
            });
            return result;
        }
    }


}
